package jmine.tec.report.impl.batch;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import jmine.tec.report.api.batch.ReportCreator;
import jmine.tec.report.export.BatchReportExporter;
import jmine.tec.utils.Tuple;

/**
 * @author lundberg
 */
public class BatchReportExporterImpl implements BatchReportExporter {

    public static final int BUFFER_SIZE = 1024;

    private static final Pattern PATTERN_FILENAME = Pattern.compile("^(.+)\\.(.+)$");

    private List<Tuple<String, InputStream>> reports;

    /**
     * Construtor
     */
    public BatchReportExporterImpl() {
        this.reports = new LinkedList<Tuple<String, InputStream>>();
    }

    /**
     * {@inheritDoc}
     */
    public void export(OutputStream outputStream) throws IOException {
        Set<String> zipEntriesNames = new LinkedHashSet<String>();
        ZipOutputStream zipOutput = new ZipOutputStream(outputStream);
        for (Tuple<String, InputStream> result : this.reports) {
            zipOutput.putNextEntry(new ZipEntry(this.getFileName(result.head(), zipEntriesNames)));
            this.copyFullyCloseEntry(result.tail(), zipOutput);
        }
        zipOutput.close();
    }

    /**
     * Adiciona um relatório ao conjunto de relatórios que será criado
     * 
     * @param report report
     * @param parameter parameter
     * @param <F> tipo de filtro do relatório
     * @throws IOException IOException
     */
    public <F> void addReport(ReportCreator<F> report, F parameter) throws IOException {
        this.reports.addAll(report.createReportFiles(parameter));
    }

    /**
     * Cria o nome do relatório
     * 
     * @param original original
     * @param zipEntriesNames zipEntriesNames
     * @return String
     */
    private String getFileName(String original, Set<String> zipEntriesNames) {
        String fileName = original.replaceAll("[^\\p{ASCII}]", "");
        int index = 1;
        while (zipEntriesNames.contains(fileName)) {
            fileName = this.generateFileName(fileName, index++);
        }
        zipEntriesNames.add(fileName);
        return fileName;
    }

    /**
     * Cria um novo nome de arquivo baseado no nome base.
     * 
     * @param baseFileName baseFileName
     * @param index index
     * @return um novo nome de arquivo
     */
    private String generateFileName(String baseFileName, int index) {
        Matcher matcher = PATTERN_FILENAME.matcher(baseFileName);
        if (matcher.matches()) {
            return matcher.group(1) + " (" + index + ")." + matcher.group(2);
        } else {
            return baseFileName + "_" + index;
        }
    }

    /**
     * Copy the bytes fully from the source into the output, closing both streams in the end.
     * 
     * @param source the source input stream
     * @param output the output
     * @throws IOException if an IOException occurs, either reading, writing or closing any of the streams.
     */
    private void copyFullyCloseEntry(final InputStream source, final ZipOutputStream output) throws IOException {
        try {
            byte[] buffer = new byte[BUFFER_SIZE];
            int read = -1;
            while ((read = source.read(buffer)) >= 0) {
                output.write(buffer, 0, read);
            }
        } finally {
            try {
                source.close();
            } finally {
                output.closeEntry();
            }
        }
    }
}
